%!TEX encoding = UTF-8 Unicode
\chapter{ARM漏洞利用}
\label{Chap:arm}
\section{学习的目的}
目前，绝大部分智能移动终端使用ARM处理器，包括基于Android、iOS、Symbian、Windows Mobile、Windows Phone等操作系统的手机和平板电脑。ARM还被大量的嵌入式设备使用。

使用ARM处理器的PC也已经出现。几年前，Debian就发布了ARM移植版；2011年，Ubuntu开始支持ARM；2012年将正式发布的Windows 8也已经被微软移植到ARM上。预计在2012年，使用ARM处理器的笔记本电脑将开始流行。

ARM还开始向服务器市场发展。目前，NVIDIA公司和巴塞罗那超级计算中心还推出了基于ARM和CUDA计算的超级计算机，NVIDIA也将在2012年初向普通开发者推出基于ARM的CUDA开发工具。

可以看出，ARM设备无论是数量上还是覆盖面都不逊色于PC。但ARM的安全问题，尤其是其上软件漏洞的问题，直到近几年才引起人们的注意。
\section{环境和工具}
\subsection{ARM设备和系统}
\subsubsection{开发板}
获得一个ARM设备的常规方法是购买或者定制ARM开发板。如果不打算移植Android等系统，可以选择mini2440，否则可以选择mini6410等型号。

需要自己为开发板交叉编译操作系统，并烧录进去。这个过程会比较好玩。但如果觉得乏味，或者时间不够，可以使用Ångstr\"om发行版\cite{url:angstrom}。

开发板的优点是，它是一个真实的ARM设备和运行环境；缺点是操作稍显繁琐。除此以外，还有其他方法可以获得ARM系统。

\subsubsection{Android模拟器或手机}
Android手机本身就是一个ARM设备，而Android底层的Linux内核就是运行于ARM上的系统。因此，Android手机基本是可以充当ARM开发和调试环境的。此外，Android SDK中的模拟器也是基于qemu模拟了ARM设备。从漏洞分析和漏洞利用的角度来看，使用Android手机或模拟器进行学习的优点包括：

\begin{itemize}
\item 如果要分析Android的漏洞，这自然是最佳的选择
\item 有完善的开发工具链和系统源码
\item 模拟器的硬件（边际）成本为0
\end{itemize}

但缺点也不少：

\begin{itemize}
\item 在Android中的Linux上，没有太多的本地应用程序，例如\lstinline!gdb!等调试工具（只有\lstinline!gdbserver!）
\item 不使用glibc库，而是自行开发的bionic，因此一些利用技巧在细节上会和Linux(on ARM)有差异
\item 目标文件的编译，或者完全静态链接，或者用NDK开发，或者与Android源码一起编译，均显繁琐
\end{itemize}

无论如何，模拟器完全没有成本，建议在PC中常备以便使用。

\subsubsection{Debian虚拟机}
早在2000年，Debian就开始了对ARM处理器的移植工程，目前其仓库中几乎所有的软件都有了ARM版本，包括\lstinline!objdump!、\lstinline!gdb!等常用开发和漏洞分析工具。

另一方面，\lstinline!qemu!模拟器已经可以完美地模拟ARM处理器及其硬件平台，可以用它在x86的PC上运行ARM版的Debian。

可以直接从Debian的官网下载到其ARM安装包，然后参考aurel提供的安装指南\cite{url:debian_arm_qemu}。

更省事的方案是直接下载已经安装好的\lstinline!qemu!镜像，也是由aurel提供。包括arm版\cite{url:debian_img_arm}和armel版\cite{url:debian_img_armel}，建议使用后者。

\subsubsection{Toshiba AC100}
Toshiba AC100是目前唯一一款拥有标准全键盘的ARM笔记本。它最初是为Android设计，但hackers已经将多个Ubuntu版本移植到该机器上。最终，Canonical公司从Ubuntu 11.10开始专门为这一机器提供预编译版本和软件源，作为其向ARM平台扩张的第一步。

该机器在国内的型号为AC100-01B，已经不再生产，但可以购买到二手的。

这个笔记本是目前唯一一种可以最轻松获得的真实ARM机器，并拥有前面提到的Debian虚拟机的一切优点。

此外，在前面所述的方案中，除了4.0以上Android，其Linux Kernel均未开启ASLR（地址空间布局随机化）特性，但在Ubuntu for AC100中，开启了ASLR。这固然为简单的ret2libc等攻击带来了困难，但另一方面，也成为几乎是唯一的分析ASLR下漏洞的环境。考虑到Android 4.0开始去掉prelink优化、全面开启ASLR特性\cite{aslr_android}，这种研究的价值是显然的。

\subsubsection{Nokia N900手机}
另一个hack利器是Nokia N900手机，这款老手机采用已经被抛弃的Maemo系统，该系统是基于Linux的手机操作系统，开放性较高。N900的键盘操作性不强，系统也适合于有hack精神的人玩。
\subsection{交叉编译工具}
进行嵌入式开发时，编译工具运行于一个平台，但生成另一个平台的指令，这种编译过程称之为交叉编译，所使用的编译工具又称之为工具链。

在x86上编译ARM代码最常用的工具链由CodeSourcery公司免费提供，有Windows版本和Linux版本\cite{url:sourcery}。

此外，在基于Debian的系统（例如Ubuntu）中，也可以通过\lstinline!apt-get install gcc-arm-linux-gnueabi!直接获得一个交叉编译gcc。

Android的NDK中实际上也包含了完整的工具链，包括编译器、调试器、binutils等。它同样有Windows和Linux版本。

然而NDK也有不足，即只能为Android编译代码，也只能使用其提供的有限的库接口。对后面一个问题，如果需要编译使用了Andrid系统中较底层API的ARM程序，建议使用\lstinline!agcc!工具，具体可以看我写的文章\footnote{\url{http://blog.claudxiao.net/2011/10/android\_agcc/}}。
\subsection{反汇编和反编译工具}
工具链包含了很多的工具，如果静态反汇编，可以使用\lstinline!objdump!；如果动态运行起来，在\lstinline!gdb!中也可以使用\lstinline!disass!反汇编。

对ARM反汇编支持最好的工具是IDA Pro，但它是商业软件。自由开源的反编译工具可以考虑radare\cite{url:radare}或者smiasm\cite{url:smiasm}，但radare和smiasm对Thumb、Thumb-2指令集的支持都不强。

对这三个工具在ARM反汇编上的对比可以参考我的文章\footnote{\url{http://blog.claudxiao.net/2011/12/arm-disassemblers}}。

\begin{table}[htbp]
  \caption{ARM反汇编器对比}
  \centering
  \begin{tabular}{lllll}
    \toprule
    方案 & 开源 & 支持Thumb & 递归反汇编 & 提供指令详情 \\
    \midrule
    smiasm & 是 & 否 & 是 & 是 \\
    radare & 是 & 是 & 否 & 否 \\
    IDA & 否 & 是 & 是 & 是\\
    \bottomrule
  \end{tabular}
\end{table}

事实上，ARM的指令编码并不复杂，自己写一个ARM反汇编器的工作量远小于想象。

目前ARM的反编译工具只有Hex-Rays ARM Decompiler，是一款昂贵的收费软件。
\subsection{远程调试}
可以使用\lstinline!gdb!远程调试ARM设备或虚拟机中的程序。假设设备的IP地址是192.168.0.2，要调试的程序名为demo，则在设备端运行：
\begin{lstlisting}[language=bash, numbers=none]
  $ gdbserver 192.168.0.2:23456 /path/to/demo
\end{lstlisting}

接下来，在主机端使用ARM工具链中的\lstinline!gdb!调试：
\begin{lstlisting}[language=bash, numbers=none]
  $ arm-eabi-gdb /path/to/demo
\end{lstlisting}

在进去\lstinline!gdb!调试会话后，键入：
\begin{lstlisting}[language=bash, numbers=none]
  (gdb) target remote 192.168.0.2:23456
\end{lstlisting}

即可开始调试。

gdb也可以远程调试Android。在Android源码编译后，out/target/product/<product>/system/bin目录下会有\lstinline!gdbserver!，可以将其push到手机的/data/local目录下，给其可执行权限，然后类似于上面的方法使用。

与上述gdb远程调试Linux系统不同的是，调试Android手机需要用\lstinline!adb!工具做端口转发，方法如下：

\begin{lstlisting}[language=bash, numbers=none]
  $ adb forward tcp:23456 tcp:23456
\end{lstlisting}

其中第一个参数是手机系统内的端口，第二个参数是本地端口。然后在本地的gdb中，\lstinline!target!的IP地址就是本地127.0.0.1了。

类似于此，还可以使用IDA Pro远程调试Android系统中的ELF可执行文件或动态链接库文件（包括NDK开发了用于APK文件的本地动态库）。步骤如下（该方法来自于Berry的文章\footnote{\url{http://debugman.com/thread/6230/1/1}}）：

\begin{enumerate}
  \item 将IDA Pro 6.1的bin目录下的\lstinline!android_server!文件\lstinline!push!到手机的/data/local目录下，并通过adb shell给其可执行权限；
  \item 在\lstinline!adb shell!里，启动这个\lstinline!android_server!程序，查看其输出，默认情况下打开23946号端口；
  \item 在本地的shell中使用\lstinline!adb!工具转发该端口：\lstinline!adb forward tcp:23946 tcp:23946!；
  \item 在IDA Pro的Debugger菜单中，选择Android调试，在其选项中，hostname填写127.0.0.1，port填写23946。如果是调试一个可执行文件，还需要在application中填上可执行文件在Android系统中的路径；input file中填写与application一致的文件及路径，并加上运行参数；在directory中填上该文件所在目录。
  \item 在IDA Pro的Debugger菜单中，如果是调试已经存在的进程，选择attach，在弹出来的进程列表中选择要调试的进程即可；如果是调试一个可执行文件，选择start process即可。
\end{enumerate}

\section{ARM体系结构}
\subsection{ARM指令集}
ARM同时是三个不同事物的名字：
\begin{itemize}
\item 一个公司
\item 一种体系结构
\item 一系列CPU产品
\end{itemize}
这一章主要是指第二种含义。

ARM是32位RISC结构，在ARMv5以后，基本采用Harvard结构，与传统的冯诺依曼结构不同的是，数据和代码被最大程度隔离了。既便于数据段保护的x86也有本质区别，ARM的代码和数据采用不同的总线传输（并因此获得并行而提速）。其数据段自然不能被执行。

\subsubsection{寄存器}
对程序员可见的寄存器主要是\lstinline!r0!到\lstinline!r15!共16个，不同的寄存器有不同的用途，将在下一节说明。

此外，有程序状态寄存器\lstinline!PSR!，其中包括算数逻辑运算标志、执行状态位、当前中断号等。

\subsubsection{数据操作指令}
基本形式是：
\begin{lstlisting}[numbers=none]
<opcode>{<cond>}{S} <Rd>, <Rn>{, <operand2>}
\end{lstlisting}
其中：
\begin{itemize}
\item[opcode] 比如\lstinline!MOV!、\lstinline!ADD!，与x86类似，具体可以查ARM手册
\item[cond] 可选的条件码。条件执行是ARM的特色之一，几乎所有的ARM指令都可以加上条件码，实现条件执行。例如，\lstinline!EQ!、\lstinline!LE!等
\item[S] 可选的\lstinline!S!位，如果该条指令会更改\lstinline!PSR!，则应置上\lstinline!S!位
\item[operand] 操作数可以是寄存器，也可以是一个右值（后面会说明），每条指令有多少操作数，分别是什么含义，应查手册
\end{itemize}

ARM中的右值操作数可以是一个寄存器、一个立即数，或者一个寄存器的移位。例如，右值\lstinline!R1, LSL #2!由\lstinline!R2!的值逻辑左移2位得到，右值\lstinline!R1, ASR R3!由\lstinline!R1!的值算术右移\lstinline!R3!位得到。

\subsubsection{分支跳转指令}
\begin{itemize}
\item[B] 直接跳转到一个地址，唯一的参数是12位立即数，与x86中的\lstinline!jmp!功能一样
\item[BL] 将下一条指令地址（即返回地址）赋给\lstinline!LR!寄存器，然后跳转到参数指定的地址，参数是一个12位立即数。\lstinline!BL!与x86中的\lstinline!call!功能一样
\item[BX] 参数是一个寄存器，跳转到该寄存器指向的地址
\end{itemize}
所有这些分支跳转指令都可以使用条件码，成为相应的条件跳转指令。

\subsubsection{内存访问指令1}
ARM使用简单的访存模型，所有内存读写都通过\lstinline!LDR!和\lstinline!STR!两条指令完成，而且只能在寄存器与内存之间读写，不能直接从内存读写到内存。\lstinline!LDR!和\lstinline!STR!都有一个可选的后缀\lstinline!B!，不选时按字（4字节）读写，选择B时按字节读写。其第一个参数是寄存器，第二个参数是内存地址。

\subsubsection{寻址方式}
第一类寻址方式：
\begin{itemize}
\item 寄存器加上立即数偏移：[reg, \#$\pm$imm12]
\item 寄存器加上寄存器偏移：[reg, $\pm$reg]
\item 寄存器a加上移位后的寄存器b偏移：[rega, $\pm$regb, shift]
\end{itemize}
这些地址符号后面可以选择一个叹号：!。如果加上，表明先根据寻址规则修改寄存器，然后根据寄存器中的值访问内存；如果不加叹号，表示直接根据寻址规则访问内存。

第二类寻址方式则是先根据寄存器中的值访问内存，然后按照相应的规则更新寄存器：
\begin{itemize}
\item 访存后，寄存器加上立即数：[reg], \#$\pm$imm12
\item 访存后，寄存器加上寄存器：[reg], $\pm$reg
\item 访存后，寄存器a加上移位后的寄存器b：[rega], $\pm$regb, shift
\end{itemize}

\subsubsection{内存访问指令2}
还可以一次性读写多个字：
\begin{lstlisting}[numbers=none]
LDMcdum reg!, mreg
STMcdum reg!, mreg
\end{lstlisting}
其中：
\begin{itemize}
\item[cd] 可选的条件
\item[um] 访问模式，分别为IA读写后增加寄存器值、DA读写后减少寄存器值、IB读写前增加寄存器值、DB读写后增加寄存器值
\item[!] 表示会修改寄存器，修改方法参考um
\item[mreg] 支持一次指令多个寄存器，例如{R0-R3, R7, R10}
\end{itemize}

\subsection{ATPCS}
\subsubsection{名词解释}
例程(routine)、子例程(subroutine)：对于一段可以调用、可以返回、保证栈平衡的代码片段，例程指调用者，子例程指被调用者。

过程(procedure)：不返回结果值的例程。

函数(function)：返回结果值的例程。

变量大小、内存对齐、字节序、复合类型等略。
\subsubsection{寄存器的用途}

ARM中有16个通用寄存器r0 – r15。其中：
\begin{itemize}
\item[r0 – r3] 用于传递参数、返回函数结果，因此又名a0 – a3。在例程内部也被用于保存临时结果
\item[r4 – r11] 用于保存例程内的局部变量值，又名v0 – v8。其中，r9(v6)是一个平台相关的值，不同的ARM平台必须为这个寄存器赋予特殊的含义
\item[r12 – r15] 有专门的用途，后面介绍。常用别名：IP、SP、LR、PC
\end{itemize}
\subsubsection{栈结构}

r13（SP）是栈指针。ARM中使用向下的满栈（full-descending），即SP始终指向最后一个已进入栈的数据，每次压栈时，SP自减需要的内存大小，然后将值存到SP新的位置。

也就是说，对栈的push相当于STMDB，对栈的pop相当于LDMIA，但一般不用这个后缀，而用STMFD和LDMFD。

SP的值应在栈的有效区间内，且mod 4 = 0。栈上有例程的调用帧结构。不要轻易地修改这个值。

\subsubsection{子例程调用}

ARM和Thumb指令集均有一个BL指令，它的操作是：将BL指令顺序下一条指令的地址（即返回地址）送入链接寄存器LR(r14)，然后将调用的目的地址（即子例程地址）送入寄存器PC(r15)。

如果在Thumb中调用BL，则LR的第0位被设为1，否则被设为0。（因为指令地址要4字节对齐，因此LR的第0和1位都没有用。）

子例程返回很简单，将LR的值送入PC即可。

任何模拟上述过程的指令序列也会起到BL的效果，例如：mov LR, PC; BX r4。注意，任何时候读PC，得到的值是当前指令地址+8（why?请自己google）。

\subsubsection{结果返回}
\begin{itemize}
\item 不超过4字节的结果，一律用r0返回
\item 超过4字节的基本类型，继续用r1, r2, r3
\item 超过4字节的复合类型，或动态大小的结果，存储在内存中，并将其地址作为调用参数之一传入
\end{itemize}
\subsubsection{参数传递}

使用r0 – r3传递参数，如果不够，使用栈。

语言中的数据类型会按照相关标准转化为机器数据类型。
\section{ARM漏洞利用的特点}
与x86相比，ARM下要做到漏洞利用存在以下问题：
\begin{itemize}
\item 从v5开始，ARM普遍采用哈佛结构，数据段不可执行
\item 函数调用时，参数传递不再通过栈，而是使用寄存器
\item 返回地址按照ATPCS是通过LR寄存器传递，大部分被掉用函数会在其代码开始时将LR再次保存到栈上，在返回前从栈中直接取回至PC寄存器
\item 由于体系结构的本质区别，作为跳板的指令无法使用x86下常见的那些，而要根据实际需要来寻找
\end{itemize}
但也有好的消息。在Windows/x86平台引入DEP以后，因为代码部分的不可执行，出现的ret2libc和ROP等技巧，其思路也可以用于ARM。此外，ARM单条指令的描述能力是超过x86的，所以要寻找到合适的跳板指令序列并不是太难。

ARM漏洞利用是一个新的领域，目前参考文献较少，主要有\cite{arm_exploiting_linux, arm_stack_exploitation, arm_exploitation, arm_ropmap, arm_alphanumeric}。
\section{简单的示例}
\subsection{源代码}
下面我们看一个实际的例子。这个程序来自于\cite{arm_exploiting_linux}，但因为编译源码使用的系统和编译器不同，后面的细节与原文有较大差异。
\lstinputlisting[language=c,caption={存在漏洞的示例代码}]{code/arm.test.c}

\subsection{反汇编结果}
如果使用的Toshiba AC100做实验，在Ubuntu下，从6.10开始gcc默认开启了-fstack-protector开关，存在不安全拷贝函数调用的函数，在退出前会做栈有效性检测（\_\_stack\_chk\_fail），因此进一步的漏洞利用会失败。应该在gcc编译时使用-fno-stack-protector来关闭这个开关。此外，在Toshiba AC100中，gcc默认将目标代码优化为Thumb指令集。后面的分析是基于ARM指令集的。可以给gcc加上-marm开关来强制指定其编译为ARM指令集。最后，也就是说，在AC100下，应该这样编译：

\begin{lstlisting}[language=bash, numbers=none]
  $ gcc test.c -o test -marm -fno-stack-protector
\end{lstlisting}

在编译完成后，就可以使用gdb来反汇编vuln()函数了。下面是在ARMEL的Debian系统下，用gcc4.4编译的结果：

\lstinputlisting[caption={vuln函数的反汇编结果}, language={}]{code/arm.test.dis1.asm}

逐一解析这些代码，以熟悉ARM指令。

在第3行，先后push了r11和lr寄存器。r11又称fp（帧指针寄存器），相当于x86下的ebp寄存器。它在该函数中使用了，所以将原来的值保存在栈中。lr前面有介绍，在调用该函数时将返回地址保存在了其中，因为在vuln()中还要bl到strcpy，还要用到lr，所以将lr保存在栈中，直到函数返回时（13行），才从栈中取出。这一点需要额外说明，虽然ATPCS规定由lr保存返回地址，且各编译器也遵循了这一约定，但在实现中，由于存在漏洞的函数其内部必然调用了其他函数，因此lr一定会在该函数的初始化时被保存到栈上，因此溢出是一定可以覆盖到它的。也就是说，虽然ATPCS的返回方法和x86的约定不同，但实际情况还是差不多。

第4行，给r11(fp)赋值。再次强调，ARM使用向下递减的满栈，因此r11指向sp加4，即栈顶第二个元素的地址。

第5行，sp自减24，开出了24字节的局部变量缓冲区。注意这里的\#24的立即数表示是十进制，而不是十六进制。

第6、7、10行，r0传给r3，r3传给r1。回忆ATPCS，r0中存着vuln()函数的第一个参数，即char *arg的值。调用strcpy时，r1是第二个参数，因此，将arg作为了strcpy的第二个参数。

第8、9行，在局部变量缓冲区取了12个字节的缓冲，给r0，作为strcpy的第一个参数。有人会奇怪，在源码中我们为buff申请了10个字节的缓冲，为什么这里成了12。这是因为ARM的内存访问都是4字节对齐的。

第11行，调用了strcpy函数。

第12行，恢复栈指针。

第13、14行，恢复r11原来的值，恢复lr的值，跳转至lr以返回。但很多编译器不这么实现，而是直接pop \{r11, pc\}。

可以看到，如果在strcpy时，输入的数据超过12个字节，就会逐步覆盖栈中保存的原来的r11（超过4字节）和lr（再超过4字节）。而lr会在后面取出作为函数返回地址。这样，就有可能通过覆盖栈来改变指令执行流程。

\subsection{栈结构}
我们再来看看当正常运行到上面第12行，即执行完strcpy后，栈的情况。我们用1234作为参数。

\lstinputlisting[caption={正常拷贝后的栈结构}, language={}]{code/arm.test.dis2.asm}

可以看到，参数1234已经出现在栈上（0xbedb9794处），而返回地址位于0xbedb97a4处，为0x0084ac。如果输入数据越界，则其17到20字节将正好覆盖这一返回地址。这个结论与我们前面对代码的分析是一致的。

\subsection{构造溢出}
从上面的栈结构，我们可以构造溢出数据了。如下所示：

\lstinputlisting[caption={构造溢出数据}, language={}]{code/arm.test.dis3.asm}

我们先看下donuts()函数的地址，为0x00008438，再考虑不破坏其他栈结构，因此构造了长为20字节的输入数据。其前12个字节随便填充，13-16字节为原来栈上的r11值，17-20字节为我们想要的返回地址，即donuts()函数的地址。最后，可以看到donuts()函数确实运行了起来，程序也正常退出了。

\section{漏洞攻击方法}
上述示例只是一个演示性的栈溢出。如果要发起真实攻击，跳到一个本地函数并不是我们需要的。

在x86中，最初的做法是将shellcode作为数据传给程序，程序将其放在溢出后的栈上。当函数退出时，跳转到栈上执行shellcode。在引入DEP后，栈上数据不可执行，从而产生了包括ret2libc在内的ROP技术，即跳转到一些系统API或库函数，通过控制这些API的参数实现一定的能力。

在x86上，参数是通过栈传递的。也就是说，通过栈溢出几乎一定可以把参数安置好，并调用其他函数。但在ARM上这一招没法直接用了。ATPCS规定使用r0 - r3这四个寄存器传递参数，而通常栈溢出是无法影响到这些寄存器的。只能寄希望于在溢出后，存在这类指令序列：将栈上的值拷贝至r0 - r3中。

以此思想为基础，Avraham提出了所谓的Ret2ZP(Return to Zero Protection)的攻击方法，主要在\cite{arm_stack_exploitation}和\cite{arm_exploitation}中进行了阐述。

\section{zergRush分析}
\subsubsection{背景和原理}
Revolutionary工具开发小组在2011年10月发布了一个在Android 2.2和2.3上获得root权限的方法\footnote{\url{http://forum.xda-developers.com/showthread.php?t=1296916}}，并公布了漏洞利用代码zergRush.c\footnote{\url{https://github.com/revolutionary/zergRush/blob/master/zergRush.c}}。tomken\_zhang已经在其博客上发表了两篇文章对其分析\footnote{\url{http://blog.csdn.net/tomken\_zhang/article/details/6866260}\newline\url{http://blog.csdn.net/tomken\_zhang/article/details/6870104}}。本文做进一步梳理和补充。

产生漏洞的主要原因是：具有root权限的vold进程使用了libsysutils.so库，该库的一个函数存在栈溢出，因此可以在root权限执行输入的shellcode。

存在漏洞的函数为FrameworkListener::dispatchCommand，位于源码的$$\backslash system\backslash core\backslash libsysutils\backslash src\backslash FrameworkListener.cpp$$中，其中的局部变量argv为固定大小的指针数组，当输入参数的数量超过其大小时，会越界写入栈中。

zergRush.c成功地利用了这一漏洞，并进一步：
\begin{enumerate}
\item 在/data/local/tmp/下增加一个置了S位的shell；
\item 使Android中后续启动的adb进程以root权限运行。
\end{enumerate}

其中第二步的方法是：adb进程最初以root运行，之后调用setuid()降低权限\footnote{\url{http://blog.claudxiao.net/2011/04/android-adb-setuid/}}降权之前，会判断系统属性ro.kernel.qemu，如果该属性位1，则不降权。

\subsubsection{函数功能概要}
\begin{description}
\item[die] 打印出错信息，退出程序
\item[copy] 将一个文件拷贝为另一个文件
\item[remount\_data] 重新mount一个分区
\item[find\_symbol] 查找libc.so中导出函数的内存地址
\item[check\_addr] 确定一个地址中是否包含被禁止的字节
\item[do\_fault] 构造溢出数据和exploit，并通过socket发送给vold进程
\item[find\_rop\_gadgets] 从libc.so中寻找两个特殊指令序列的地址
\item[checkcrash] 调用do\_fault，判断其溢出产生的调试信息中是否包含sp
\item[find\_stack\_addr] 调用do\_fault，从其溢出产生的调试信息中定位栈地址
\item[do\_root] 将shell文件的S位置上，并设置ro.kernel.qemu属性为1
\item[main] 主函数，完成漏洞利用的所有步骤
\end{description}

\subsubsection{main函数}
\lstinputlisting[language=c,caption={zergRush的main函数}, firstnumber=387, firstline=387, lastline=536]{code/zergRush.c}
\begin{itemize}
\item[395-396] 如果当前程序是以root权限运行，并且程序名为boomsh，则调用do\_root，执行附加的两步操作
\item[402-405] 将自身拷贝至/data/local/tmp/boomsh，并设置其权限为0711，将/system/bin/sh拷贝至/data/local/tmp/sh。
\item[407-408] 根据/system/bin/vold文件的大小获得其对应进程中堆的大概地址heap\_addr。
\item[410-421] 根据系统版本对heap\_addr做微调。如果不是2.2或2.3系统，退出。
\item[423-428] 查询libc.so中system调用的地址，保存至system\_ptr。
\item[430-443] 通过checkcrash函数，判断buffsz为16或24时能否成功利用。这里buffsz实际指libsysutils中造成栈溢出的指针数组argv的容量。
\item[445-484] 调用find\_stack\_addr函数，确定栈地址。反复尝试五次，每次对堆地址heap\_addr做微调，直至成功。判断得到的栈地址是否有效。
\item[486-487] kill掉当前的logcat进程，删除/data/local/tmp/crashlog文件。
\item[489-491] 调用find\_rop\_gadgets函数，在libc.so中寻找指令序列add sp, \#108; pop \{r4-r7, pc\}，将地址保存在stack\_pivot；寻找指令pop \{r0, pc\}，将地址保存在pop\_r0。
\item[493-514] 尝试三次，每次调用do\_fault，之后判断/data/local/tmp/sh的S位是否置上，一旦置上，则利用成功；否则，微调栈地址heap\_addr（加减16）。
\item[516-533] 一旦利用成功，并且系统的ro.kernel.qemu属性已经被置为1，则利用完成，重启的adb进程即可获得root权限。
\end{itemize}

\subsubsection{do\_root函数}
\lstinputlisting[language=c,caption={zergRush的do\_root函数}, firstnumber=377, firstline=377, lastline=384]{code/zergRush.c}
\begin{itemize}
\item[395-396] 若当前程序是以root权限执行的/data/local/tmp/boomsh，则调用do\_root函数。
\item[379] 重新mount目录/data。
\item[380] 将/data/local/tmp/sh的所有者设置为root。
\item[381] 将/data/local/tmp/sh的属性设置为04711，注意其S位被置位。
\item[382] 设置系统的ro.kernel.qemu属性为1。
\end{itemize}

\subsubsection{find\_stack\_addr函数}
\lstinputlisting[language=c,caption={zergRush的find\_stack\_addr函数}, firstnumber=324, firstline=324, lastline=374]{code/zergRush.c}
\begin{itemize}
\item[332-333] 清空logcat缓存，删除老的/data/local/tmp/crashlog日志文件。
\item[335-340] 重启一个logcat，将其日志输出至/data/local/tmp/crashlog文件。
\item[342-349] 调用一次do\_fault，等待3秒后，读取crashlog文件中的logcat日志。
\item[350-366] 搜索logcat日志中的debug信息，"4752455a"之前8个字节为栈基址stack\_addr，"5245564f"往之前8个字节为over，"sp"之后5个字节为栈顶sp。
\item[370-371] jumpsz = over – sp
\end{itemize}

\subsubsection{do\_fault函数}
\lstinputlisting[language=c,caption={zergRush的do\_fault函数}, firstnumber=163, firstline=163, lastline=221]{code/zergRush.c}
\begin{itemize}
\item[165] buf是最后发送至vold的shellcode。
\item[169-181] padding是shellcode中的一段填充内容，全部为Z，无意义。长度为padding\_sz + 12。padding\_sz由108减去jumpsz计算得到。
\item[183-184] 通过socket连到本地的vold进程。
\item[186-190] 将栈地址stack\_addr、指令序列1地址stack\_pivot、指令序列2地址pop\_r0、system调用地址system\_ptr、堆地址heap\_addr，分别填充到相应的字符串中。
\item[192-198] 开始构造shellcode。注意第195行，这里根据buffsz，也就是尝试出来的溢出数组argv的大小，构造相应数量的输入参数。
\item[200-201] 计算一下/data/local/tmp/boomsh字符串将会出现的地址，这个字符串会作为shellcode的一部分发到栈中，因此可以根据栈地址和偏移计算出来，最后作为system调用的参数。
\item[208] 把上述地址转为字符串s\_bsh\_addr。
\item[209] 进一步构造shellcode，包括栈地址、堆地址、填充、指令序列2地址、boomsh字符串地址、system调用地址、boomsh字符串等。
\item[214] 将shellcode发送至vold进程。
\end{itemize}

\subsubsection{总结}

综合来看，zergRush.c的思路如下：
\begin{enumerate}
\item 计算出vold的堆地址
\item 查到system调用的地址
\item 尝试出栈缓冲区大小
\item 通过崩溃产生的调试信息，取得栈地址和栈结构信息
\item 在libc.so中找寻跳板指令
\item 根据缓冲区大小、栈结构和上述各种地址，构造出有效的shellcode来，发送到vold
\item shellcode在vold中以root权限运行，它通过system调用运行该利用程序的一个副本boomsh
\item 程序副本boomsh以root权限运行时，会置上shell程序的S位，并设置系统属性ro.kernel.qemu
\item 结束掉adb，后续开启的adb进程将具有root权限
\end{enumerate}

非常典型的缓冲区溢出利用思路，但与PC相比，利用了android中几个特殊之处：
\begin{itemize}
\item vold的溢出会在adb logcat中输出调试信息，这些信息说明了其内存结构，而其他程序可以读取到这些信息；
\item 在ARM架构下，跳板指令有了更多的选择，ret2libc的攻击也可能更容易实现
\item adb的降低权限过程又一次被利用。
\end{itemize}

最后，我们没有进一步分析shellcode的详细结构和跳转过程，难度已经不大。反而是libsysutils.so这个通用库中的溢出有没有可能造成其他问题，需要进一步分析。

